#! -*- coding: utf-8 -*-
from open4d import *

_4D_Init4DWS() #init DLL on import

from ctypes import *
import datetime

def Date4Dtodate(date4d):
    if max(date4d.Year,date4d.Month,date4d.Day) == 0:
        return None
    return datetime.datetime(date4d.Year,date4d.Month,date4d.Day)

def TE4Dtotext(te4d):
    m = Lock4DHandle(te4d.hText)
    text = cast(m,c_char_p)
    Unlock4DHandle(te4d.hText)
    if text.value:
        text = text.value.decode('macroman')[:te4d.TextLen]
    else:
        text = ''
    return text

def st(chaine,st_typ = st32):
    if isinstance(chaine,unicode):
        chaine = chaine.encode('macroman')
    s = st_typ()
    s[0] = len(chaine)
    i = 1
    for p in chaine:
        s[i] = ord(p)
        i+=1
    return s

def unst(st_object):
    chaine = ''
    for i in range(st_object[0]):
        chaine += chr(st_object[i+1])
    chaine = chaine.decode('macroman')
    return chaine

def realtofloat(real):
    return real.r

def strtost80(chaine):
    return st(chaine,st80)

def floattoreal(fl):
    f = Real()
    f.r = fl
    return f

#search operator
None1 = 0
Equal =	1
Diff = 2
Greater_than = 3
Greater_or_Equal = 4
Less_than = 5
Less_or_Equal = 6
Contains = 7
Does_not_contain = 8

#logic operator
None2 = 0
Logic_And = 1
Logic_Or = 2
Except = 3

#data type convert

DATA_TYPE = {0:'s',
            1:'r',
            2:'text',
            3:'pic',
            4:'d',
            6:'b',
##            7:'s_SubFile',
            8:'i',
            9:'l',
            11:'tim',
            30:'blob',
            }


def get_value_datarec(datarec):
    """
    retrieve the value of datarec using typ
    """
    key = DATA_TYPE.get(datarec.typ)
    if key:
        value = getattr(datarec.u,key)
    value = convert_value(value)
    return value

def set_value_datarec(value,typ):
    """
    convert value to 4d datarec
    """
    datarec = DataRec()
    datarec.typ = typ
    key = DATA_TYPE.get(datarec.typ)
    if key:
        value = unconvert_value(value)
        setattr(datarec.u,key,value)
    return datarec


CONVERT_TYPE = {st15:unst,
                st32:unst,
                st80:unst,
                Date4D:Date4Dtodate,
                Real:realtofloat,
                TE4D:TE4Dtotext,
                }

CONVERT_TYPE2 = {str:strtost80,
                unicode:strtost80,
                float:floattoreal,
                }

def convert_value(value):
    """
    convert c/pascal variable to python variable
    """
    func = CONVERT_TYPE.get(value.__class__)
    if func:
        value = func(value)
    return value

def unconvert_value(value):
    func = CONVERT_TYPE2.get(value.__class__)
    if func:
        value = func(value)
    return value

def create_pstring(chaine):
    myst = c_ubyte * 255
    s = myst()
    s[0] = len(chaine)
    i = 1
    for p in chaine:
        s[i] = ord(p)
        i+=1
    return pstring(s)

class Open4D:
    def __init__(self):
        self.CIDH = None
        self.ComponentNumber = None
        self.NetworkComponent = None
        self.NetworkLocation = None

    def InitNetworkComponent(self,ComponentNumber=2):
        """c_short and return NetworkComponentHandle"""
        self.ComponentNumber = ComponentNumber
        self.NetworkComponent = _4D_InitNetworkComponent(ComponentNumber)
        if self.NetworkComponent is None:
            raise Exception("Echec à l'initialisation du composant réseau")

    def DeInitNetworkComponent(self):
        """c_short, NetworkComponentHandle"""
        err = _4D_DeInitNetworkComponent(self.ComponentNumber,self.NetworkComponent)
        return err

    def __del__(self):
        self.Close4DConnection()

    def NB_NetworkComponent(self):
        """return the number of network component"""
        nb = _4D_NB_NetworkComponent()
        return nb

    def GetComponentInfo(self,IndNumber):
        """c_short, byref(c_short), byref(pstring)
        return err"""
        ComponentNumber = int2()
        ComponentName = create_pstring('')
        _4D_GetComponentInfo(IndNumber, ComponentNumber, ComponentName)
        return (ComponentNumber.value,unst(ComponentName.contents))

    def GetComponentInfo_list(self):
        lst = []
        for i in range(1,self.NB_NetworkComponent()+1):
            lst.append(self.GetComponentInfo(i))
        return lst

    def GetServerInfo(self):
        """ConnectHandle, byref(ServerInfoRec)"""
        ServerInfo = ServerInfoRec()
        err = _4D_GetServerInfo(self.CIDH, ServerInfo)
        return ServerInfo

    def Select4DServer(self):
        """pstring, byref(NetworkLocationPtr),c_short,NetworkComponentHandle
        return err"""
        Name = create_pstring('')
        self.NetworkLocation = NetworkLocationPtr()
        err = _4D_Select4DServer(Name, self.NetworkLocation, self.ComponentNumber,self.NetworkComponent)
        return err

    def Find4DServer(self,Name):
        """pstring, byref(NetworkLocationPtr),c_short,NetworkComponentHandle
        return err"""
        Name = create_pstring(Name)
        self.NetworkLocation = NetworkLocationPtr()
        err = _4D_Find4DServer(Name, self.NetworkLocation, self.ComponentNumber,self.NetworkComponent)
        return err

    def connect(self,username,password,process_name='py4d'):
        import socket
        ID = IDRec(StationName = st(socket.getfqdn()),UserName = st(username), UserPass = st(password),TaskName = st(process_name))
        return self.Open4DConnection(ID)

    def new_process(self):
        """
        start a new process using the same 4d server connection
        """
        client = Open4D()
        client.ComponentNumber = self.ComponentNumber
        client.NetworkComponent = self.NetworkComponent
        client.NetworkLocation = self.NetworkLocation
        return client

    def close(self):
        self.Close4DConnection()
        self.DeInitNetworkComponent()

    def Open4DConnection(self,ID):
        """byref(IDRec),NetworkLocationPtr,byref(ConnectHandle),c_short,NetworkComponentHandle
        return err"""
        self.CIDH = ConnectHandle()
##        err.value = _4D_Open4DConnection(byref(ID), self.NetworkLocation, byref(self.CIDH), self.ComponentNumber, self.NetworkComponent)
        err = _4D_Open4DConnection(ID, self.NetworkLocation, self.CIDH, self.ComponentNumber, self.NetworkComponent)
        return err

    def Close4DConnection(self):
        """Close connection
        return err"""
        err = _4D_Close4DConnection(self.CIDH)
        return err

    def FlushServer(self):
        """ConnectHandle
        return err"""
        err = _4D_FlushServer(self.CIDH)
        return err

    def SetConnectionAsync(self):
        """ConnectHandle
        return err"""
        err = _4D_SetConnectionAsync(self.CIDH)
        return err

    def SetConnectionSync(self):
        """ConnectHandle
        return err"""
        err = _4D_SetConnectionSync(self.CIDH)
        return err

    def Process4DRequest(self):
        """ConnectHandle
        return err"""
        err = _4D_Process4DRequest(self.CIDH)
        return err

    def IsConnectionAsync(self):
        """ConnectHandle
        return err"""
        AsyncMode = byte()
        err = _4D_IsConnectionAsync(self.CIDH,AsyncMode)
        return AsyncMode.value

    def get_filenumber(self,filename):
        """
        return the file number from the filename
        this function use GetFileList()
        this function is case insensitive
        """
        from string import upper
        lst = self.GetFileList()
        lst = map(upper,lst)
        filename = filename.upper()
        if filename in lst:
            return lst.index(filename)+1
        else:
            return -1


    def SelectAllRecords(self,Targetfile):
        """ConnectHandle,c_short
        return err"""
        err = _4D_SelectAllRecords(self.CIDH, Targetfile)
        return err

    def RecordsInSelection(self,Targetfile):
        """
        return the number of record in selection
        """
        count = int4()
        err = _4D_RecordsInSelection(self.CIDH,Targetfile,count)
        return count.value

    def GetFirst_Nth_Values(self,TargetFile,TargetField,MaxRecords):
        """
        select the Nth record in TargetFile using TargetField as index
        if TargetFile is negative, start from the end
        """
        err = _4D_GetFirst_Nth_Values(self.CIDH,TargetFile,TargetField,MaxRecords)
        return err

    def GotoSelectedRecord(self,Targetfile,RecNumberInSel):
        """
        select a record by number in selection
        """
        err = _4D_GotoSelectedRecord(self.CIDH, Targetfile, RecNumberInSel)
        return err

    def GotoRecord(self,Targetfile,RecNumber):
        """
        select a record by number
        """
        err = _4D_GotoRecord(self.CIDH, Targetfile, RecNumber)
        return err

    def ShrinkSelection(self,Targetfile,MaxRecords):
        """
        reduce the current selection to a number of records
        """
        err = _4D_ShrinkSelection(self.CIDH,Targetfile,MaxRecords)
        return err

    def GetFields(self,TargetFile,fields_list):
        """
        return fields values of the current record
        """
        if not isinstance(fields_list,list):
            fields_list = [fields_list]
        f = ReqFieldRec()
        f.TargetFile = TargetFile
        f.NB_Fields = len(fields_list)
        for i,field in enumerate(fields_list):
            f.Fields[i] = field
        Buffer = BufferHandle()
        IsLocked = byte()
        err = _4D_GetFields(self.CIDH,ReqFieldRecPtr(f),Buffer,IsLocked)

        values = []
        for field in range(len(fields_list)):
            field += 1
            Field = DataRec()
            _4D_GetNthField(self.CIDH,Buffer,field,Field)
            values.append(get_value_datarec(Field))
        if IsLocked:
            _4D_UnlockCurrentRecord(self.CIDH,TargetFile)
        Free4DHandle(Buffer)

        return values

    def get_selection(self,TargetFile,fields_list):
        """
        return fields values of the current selection to a list
        """
        nb = self.RecordsInSelection(TargetFile)
        lst = []
        for i in range(nb):
            self.GotoSelectedRecord(TargetFile,i+1)
            lst.append(self.GetFields(TargetFile,fields_list))
        return lst


    def SelectionToArray(self,TargetFile,fields_list):
        """
        return fields values of the current selection to a list
        """
        f = ReqFieldRec()
        f.TargetFile = TargetFile
        f.NB_Fields = len(fields_list)
        for i,field in enumerate(fields_list):
            f.Fields[i] = field
        BufferArray = ArrayHandle()
        err = _4D_SelectionToArray(self.CIDH,ReqFieldRecPtr(f),BufferArray)
        buf = Lock4DHandle(BufferArray)
        array = cast(buf,PINT_PTR)
        nb_records = array.contents.value
        for i in range(nb_records):
            a = pointer(buf)
            print a
##            print buf
##            array = cast(buf+5,PINT_PTR)
##            print array.contents.value
##            a = DataRec()
##            memmove(buf+4,a.u.s,array.contents.value)
##            print unst(a.u.s)


        Unlock4DHandle(BufferArray)
        Free4DHandle(BufferArray)
        return nb_records,array

    def search_simple(self,TargetFile,Field,operator,value):
        return self._search_simple(TargetFile,Field,operator,value)

    def searchinselection_simple(self,TargetFile,Field,operator,value):
        return self._search_simple(TargetFile,Field,operator,value,True)

    def _search_simple(self,TargetFile,Field,operator,value,in_selection=False):
        """
        search in TargetFile where Field match to value according to operator
        operator can be
        None1 = 0
        Equal =	1
        Diff = 2
        Greater_than = 3
        Greater_or_Equal = 4
        Less_than = 5
        Less_or_Equal = 6
        Contains = 7
        Does_not_contain = 8
        """
        RecordsFound = int4()
        What = SearchRecord()
        What.NB_Lines = 1
        What.TargetFile = TargetFile
        What.lines[0].Field_Number = Field
        What.lines[0].File_Number = TargetFile
        What.lines[0].LOP = 0
        What.lines[0].SOP = operator
        tabledef = self.GetFileDefinition(TargetFile)
        fielddef = tabledef.Fields[Field-1]
        What.lines[0].Value = set_value_datarec(value,fielddef.Typ)
##        What.lines[0].Value.typ = fielddef.Typ
##        What.lines[0].Value.u.s = st(value,st80)
        if in_selection:
            err = _4D_SearchInSelection(self.CIDH,SearchRecordPtr(What),RecordsFound)
        else:
            err = _4D_Search(self.CIDH,SearchRecordPtr(What),RecordsFound)
        return RecordsFound.value

    def sort_simple(self,TargetFile,Field,dir='+'):
        DIR = {'+':1,'-':0}
        What = SortRecord()
        What.NB_Lines = 1
        What.TargetFile = TargetFile
        What.lines[0].Field_Number = Field
        What.lines[0].File_Number = TargetFile
        What.lines[0].Ascent = DIR.get(dir,1)
        err = _4D_Sort(self.CIDH,What)


    def Search(self,What):
        err = c_short()
        RecordsFound = c_long()
        err.value = self.open4d._4D_Search(self.CIDH,What,byref(RecordsFound))
        return RecordsFound

      # Process information routines
    def GetListProcess(self):
        lst = ListProcessHandle()
        err = _4D_GetListProcess(self.CIDH,lst)
        plist = Lock4DHandle(lst)
        m = cast(plist,ListProcessPtr)
        Unlock4DHandle(lst)
        return m.contents

    def GetNbUsers(self):
        """return the number of user"""
        NbUsers = int2()
        err = _4D_GetNbUsers(self.CIDH,NbUsers)
        return NbUsers.value

    def GetNbUserProcesses(self):
        """Return the number of user process"""
        NbProcesses = int2()
        err = _4D_GetNbUserProcesses(self.CIDH,NbProcesses)
        return NbProcesses.value

    def Server_CurrentTime(self):
        """
        return the current server time in seconds
        """
        currenttime = int4()
        err = _4D_Server_CurrentTime(self.CIDH,currenttime)
        return currenttime.value

    def Server_CurrentDate(self):
        """
        return the current server date as datetime.date
        """
        currentdate = Date4D()
        err = _4D_Server_CurrentDate(self.CIDH,currentdate)
        return Date4Dtodate(currentdate)

    def Server_CurrentDateTime(self):
        """
        return the current server datetime as datetime.datetime
        """
        currentdate = self.Server_CurrentDate()
        currenttime = self.Server_CurrentTime()
        current_datetime = datetime.datetime(currentdate.year,currentdate.month,currentdate.day)
        current_datetime += datetime.timedelta(seconds=currenttime)
        return current_datetime

    def CountFiles(self):
        """
        return the number of files (tables)
        """
        count = int2()
        err = _4D_CountFiles(self.CIDH, count)
        return count.value

    def GetFileList(self):
        """
        return the list of file (tables)
        """
        filelist = FileListHandle()
        err = _4D_GetFileList(self.CIDH, filelist)
        plist = Lock4DHandle(filelist)
        m=cast(plist,FileListPtr)
        tables = []
        for i in range(m.contents.nbFiles):
            tables.append(unst(m.contents.fileNames[i]))
        Unlock4DHandle(filelist)
        return tables

    def GetFileDefinition( self, Targetfile):
        Fdef = FileDefHandle()
        err = _4D_GetFileDefinition( self.CIDH, Targetfile, Fdef)
        plist = Lock4DHandle(Fdef)
        m=cast(plist,FileDefPtr)
        Unlock4DHandle(Fdef)
        return m.contents

    def GetFieldNumbers(self,file_field_list):
        """
        file_field_list is a list of tuple (tablename,fieldname)
        """
        r_list = ReqFieldNames()
        r_list.NB_NameCouples = len(file_field_list)
        for i,couple in enumerate(file_field_list):
            filename,fieldname = couple
            r_list.Names[i].FileName = st(filename)
            r_list.Names[i].FieldName = st(fieldname)
        ReqField = ReqFieldRecHandle()
        err = _4D_GetFieldNumbers(self.CIDH,ReqFieldNamesPtr(r_list),ReqField)
        plist = Lock4DHandle(ReqField)
        m = cast(plist,ReqFieldRecPtr)
        tables = []
        for i in range(m.contents.NB_Fields):
            tables.append((m.contents.Fields[2*i],m.contents.Fields[2*i+1]))
        Unlock4DHandle(ReqField)
        return tables

    def get_fieldnumbers(self,tablename,fieldname_list):
        lst = []
        for f in fieldname_list:
            lst.append((tablename,f))
        lst = self.GetFieldNumbers(lst)
        lst2 = []
        for t,f in lst:
            lst2.append(f)
        return lst2


if __name__ == "__main__":

    o = Open4D()
    o.InitNetworkComponent()
    o.Find4DServer('192.168.200.204')
##    o.Select4DServer()
    o.connect('Super_Utilisateur','test')

    o.close()

